﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Xml.Serialization;
using Xmef.Interfaces;
using Xmef.Library;
using Xmef.Serializer;

namespace XmlDataReader
{
    using System.IO;
    using System.IO.Compression;
    using System.Xml;
    using System.Xml.Schema;

    [Serializable]
    public class DataV3
    {
        public static readonly IXmlRuntimeFormatter Formatter = new XmlFormatter();
        public static readonly IXmlStringSerializer<DataV3> XmlSerializer = new XmlUnicodeStringSerializer<DataV3>();
        
        private string _text;
        private List<Cell> _cells = new List<Cell>();
        private List<CellPositionValue> _moves = new List<CellPositionValue>();
        private List<CellPositionValue> _history = new List<CellPositionValue>();
        private CellData _data;
        private long _rating;

        public string Text { get { return _text; } set { _text = value; } }
        public List<Cell> Cells { get { return _cells; } set { _cells = value; } }
        public List<CellPositionValue> Moves { get { return _moves; } set { _moves = value; } }
        public List<CellPositionValue> History { get { return _history; } set { _history = value; } }
        public CellData Data { get { return _data; } set { _data = value; } }
        public long Rating { get { return _rating; } set { _rating = value; } }

        public override string ToString()
        {
            return XmlSerializer.Serialize(this, Formatter);
        }

        #region Nested type: XmlFormatter
        private class XmlFormatter : XmlTypeSerializer<DataV3>
        {
            public XmlFormatter() : base(true) { }
            public override void AddOverrides(IXmlTypeFormatter formatter)
            {
                AddType(formatter, "board");
                AddElement(formatter, "Rating", "rating");
                AddElement(formatter, "Text", "text");
                AddElement(formatter, "Data", "data", typeof(CellData));

                AddArray(formatter, "Cells", "cells");
                AddArrayItem(formatter, "Cells", "cell", typeof(Cell));

                AddArray(formatter, "Moves", "moves");
                AddArrayItem(formatter, "Moves", "move", typeof(CellPositionValue));

                AddArray(formatter, "History", "history");
                AddArrayItem(formatter, "History", "move", typeof(CellPositionValue));

                CellData.Formatter.AddOverrides(formatter);
                Cell.Formatter.AddOverrides(formatter);
                CellPositionValue.ValueXmlFormatter.AddOverrides(formatter);
            }
        }
        #endregion
    }

    [Serializable]
    public class BoardDrawing : PluginIdentity
    {
        public static readonly IXmlRuntimeFormatter Formatter = new XmlFormatter();
        public static readonly IXmlStringSerializer<BoardDrawing> XmlSerializer = new XmlUnicodeStringSerializer<BoardDrawing>();
        private static readonly IXmlStringSerializer<List<PointF>> InternalSerializer = new XmlUnicodeStringSerializer<List<PointF>>();
        
        private string _data;
        private List<PointF> _points = new List<PointF>();

        public string Data
        {
            get
            {
                _data = Serialize(_points, false, false);
                return _data;
            }
            set
            {
                _data = value;
                _points = Deserialize(_data, false, false);
            }
        }

        public override string ToString()
        {
            return XmlSerializer.Serialize(this, Formatter);
        }

        private static List<PointF> Deserialize(string data, bool useCompression, bool useXmlSerialization)
        {
            List<PointF> obj = null;
            if (data.Length > 0)
            {
                obj = new List<PointF>();
                try
                {
                    string text;
                    if (useCompression)
                    {
                        text = Encoding.UTF8.GetString(Decompress(Convert.FromBase64String(data)));
                    }
                    else
                    {
                        text = data;
                    }
                    if (useXmlSerialization)
                    {
                        return InternalSerializer.Deserialize(data);
                    }
                    else
                    {

                        string[] values = text.Split(';');
                        foreach (string value in values)
                        {
                            string[] point = value.Split(' ');
                            if (point.Length == 2)
                            {
                                float x, y;
                                if (float.TryParse(point[0], NumberStyles.Float, CultureInfo.InvariantCulture, out x) && float.TryParse(point[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y))
                                {
                                    obj.Add(new PointF(x, y));
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.WriteErrorSync(ex);
                }
            }
            return obj;
        }
        private static string Serialize(List<PointF> points, bool useCompression, bool useXmlSerialization)
        {
            StringBuilder sb = null;
            string obj = null;
            if (points.Count > 0)
            {
                try
                {
                    if (useXmlSerialization)
                    {
                        return InternalSerializer.Serialize(points);
                    }
                    else
                    {
                        IEnumerator<PointF> enumerator = points.GetEnumerator();
                        sb = new StringBuilder();
                        if (enumerator.MoveNext())
                        {
                            PointF point = enumerator.Current;
                            sb.AppendFormat("{0:4} {1:4}", point.X.ToString(CultureInfo.InvariantCulture), point.Y.ToString(CultureInfo.InvariantCulture));
                            while (enumerator.MoveNext())
                            {
                                sb.Append(';');
                                point = enumerator.Current;
                                sb.AppendFormat("{0:4} {1:4}", point.X.ToString(CultureInfo.InvariantCulture), point.Y.ToString(CultureInfo.InvariantCulture));
                            }
                        }
                    }
                    if (useCompression)
                    {
                        obj = Convert.ToBase64String(Compress(Encoding.UTF8.GetBytes(sb.ToString())));
                    }
                    else
                    {
                        obj = sb.ToString();
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.WriteErrorSync(ex);
                }
            }
            return obj;
        }
        private static byte[] Compress(byte[] bytes)
        {
            using (var ms = new MemoryStream())
            {
                using (var gs = new GZipStream(ms, CompressionMode.Compress, true))
                {
                    gs.Write(bytes, 0, bytes.Length);
                }
                ms.Position = 0;
                return ToByteArray(ms);
            }
        }
        private static byte[] Decompress(byte[] bytes)
        {
            using (var ms = new MemoryStream())
            {
                ms.Write(bytes, 0, bytes.Length);
                using (var gs = new GZipStream(ms, CompressionMode.Decompress, true))
                {
                    ms.Position = 0;
                    return ToByteArray(gs);
                }
            }
        }
        private static byte[] ToByteArray(Stream stream)
        {
            int count = 0;
            var result = new List<byte>();
            try
            {
                var buffer = new byte[131072];
                int bytes = 0;
                while ((bytes = stream.Read(buffer, 0, 131072)) > 0)
                {
                    count += bytes;
                    for (int i = 0; i < bytes; i++)
                    {
                        result.Add(buffer[i]);
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrorSync(ex);
                return null;
            }
            LogHelper.Write(string.Format(CultureInfo.InvariantCulture, "streaming data: {0} bytes", count));
            return result.ToArray();
        }

        #region Nested type: XmlFormatter
        private class XmlFormatter : XmlTypeSerializer<BoardDrawing>
        {
            public XmlFormatter() : base(true) { }
            public override void AddOverrides(IXmlTypeFormatter formatter)
            {
                AddType(formatter, "value");
                AddElement(formatter, "Data", "data");
            }
        }
        #endregion
    }

    [Serializable]
    public class CellData
    {
        public static readonly IXmlRuntimeFormatter Formatter = new XmlFormatter();
        public static readonly IXmlStringSerializer<CellData> XmlSerializer = new XmlUnicodeStringSerializer<CellData>();
        
        private List<BoardDrawing> _drawings = new List<BoardDrawing>();
        private List<CellPositionValue> _moves = new List<CellPositionValue>();
        private PointCollection _selection = new PointCollection();

        public List<CellPositionValue> Moves
        {
            get
            {
                return _moves;
            }
            set
            {
                _moves = value;
            }
        }
        public List<BoardDrawing> Drawings
        {
            get
            {
                return _drawings;
            }
            set
            {
                _drawings = value;
            }
        }
        public PointCollection Selection
        {
            get
            {
                return _selection;
            }
            set
            {
                _selection = value;
            }
        }

        public override string ToString()
        {
            return XmlSerializer.Serialize(this, Formatter);
        }

        #region Nested type: XmlFormatter
        private class XmlFormatter : XmlTypeSerializer<CellData>
        {
            public XmlFormatter() : base(true) { }
            public override void AddOverrides(IXmlTypeFormatter formatter)
            {
                AddType(formatter, "data");

                AddArray(formatter, "Drawings", "drawing");
                AddArrayItem(formatter, "Drawings", "points", typeof(BoardDrawing));

                AddArray(formatter, "Moves", "moves");
                AddArrayItem(formatter, "Moves", "reference", typeof(CellPositionValue));

                AddElement(formatter, "Selection", "selection");

                BoardDrawing.Formatter.AddOverrides(formatter);
                CellPositionValue.ValueXmlFormatter.AddOverrides(formatter);
            }
        }
        #endregion
    }

    [Serializable]
    [XmlInclude(typeof(CellPositionValueData))]
    public class CellPositionValue
    {
        public static readonly IXmlRuntimeFormatter ValueXmlFormatter = new XmlFormatter();
        public static readonly IXmlStringSerializer<CellPositionValue> ValueXmlSerializer = new XmlUnicodeStringSerializer<CellPositionValue>();

        private int _x;
        private int _y;
        private int _value;

        public int X { get { return _x; } set { _x = value; } }
        public int Y { get { return _y; } set { _y = value; } }
        public int Value { get { return _value; } set { _value = value; } }

        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "[{0},{1}] ({2})", _x, _y, _value);
        }

        #region Nested type: XmlFormatter
        private class XmlFormatter : XmlTypeSerializer<CellPositionValue>
        {
            public XmlFormatter() : base(true) { }
            public override void AddOverrides(IXmlTypeFormatter formatter)
            {
                AddType(formatter, "move");
                AddAttribute(formatter, "X", "x");
                AddAttribute(formatter, "Y", "y");
                AddAttribute(formatter, "Value", "value");
                AddOverrides(formatter, typeof(CellPositionValueData), attributes =>
                {
                    XmlTypeAttribute type = new XmlTypeAttribute("complex");
                    if (attributes.XmlType == null)
                    {
                        attributes.XmlType = type;
                    }
                });
                AddOverrides(formatter, typeof(CellPositionValueData), "Data", attributes =>
                {
                    XmlElementAttribute type = new XmlElementAttribute("data");
                    attributes.XmlElements.Add(type);
                });
            }
        }
        #endregion
    }

    [Serializable]
    public class CellPositionValueData : CellPositionValue
    {
        public static readonly IXmlRuntimeFormatter DataXmlFormatter = new XmlFormatter();
        public static readonly IXmlStringSerializer<CellPositionValueData> DataXmlSerializer = new XmlUnicodeStringSerializer<CellPositionValueData>();

        private CellData _data = new CellData();
        
        public CellData Data
        {
            get
            {
                return _data;
            }
            set
            {
                _data = value;
            }
        }

        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "[{0},{1}] {2}", X, Y, Value);
        }

        #region Nested type: XmlFormatter
        private class XmlFormatter : XmlTypeSerializer<CellPositionValueData>
        {
            public XmlFormatter() : base(true) { }
            public override void AddOverrides(IXmlTypeFormatter formatter)
            {
                AddType(formatter, "move");
                AddAttribute(formatter, "X", "x");
                AddAttribute(formatter, "Y", "y");
                AddAttribute(formatter, "Value", "value");
                AddElement(formatter, "Data", "data", typeof(CellData));
                CellData.Formatter.AddOverrides(formatter);
            }
        }
        #endregion
    }

    [Serializable]
    public class PointCollection : IXmlSerializable
    {
        private List<Point> _points = new List<Point>();
        #region IXmlSerializable Members
        public XmlSchema GetSchema() { return null; }
        public void ReadXml(XmlReader reader)
        {
            try
            {
                if (reader.IsStartElement("selection"))
                {
                    if (reader.MoveToAttribute("points"))
                    {
                        if (reader.ReadAttributeValue())
                        {
                            foreach (Point p in Deserialize(reader.Value))
                            {
                                if (p.X < 0 || p.X > 8) continue;
                                if (p.Y < 0 || p.Y > 8) continue;
                                _points.Add(p);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrorSync(ex);
            }
        }
        public void WriteXml(XmlWriter writer)
        {
            try
            {
                if (_points.Count > 0) writer.WriteAttributeString("points", Serialize(_points));
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrorSync(ex);
            }
        }
        private static List<Point> Deserialize(string data)
        {
            List<Point> obj = null;
            if (data.Length > 0)
            {
                obj = new List<Point>();
                try
                {
                    string[] values = data.Split(';');
                    foreach (string value in values)
                    {
                        string[] point = value.Split(' ');
                        if (point.Length == 2)
                        {
                            int x, y;
                            if (int.TryParse(point[0], out x) && int.TryParse(point[1], out y)) obj.Add(new Point(x, y));
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.WriteErrorSync(ex);
                }
            }
            return obj;
        }
        private static string Serialize(List<Point> points)
        {
            StringBuilder sb = null;
            string obj = null;
            if (points.Count > 0)
            {
                try
                {
                    IEnumerator<Point> enumerator = points.GetEnumerator();
                    sb = new StringBuilder();
                    if (enumerator.MoveNext())
                    {
                        PointF point = enumerator.Current;
                        sb.AppendFormat("{0} {1}", point.X, point.Y);
                        while (enumerator.MoveNext())
                        {
                            sb.Append(';');
                            point = enumerator.Current;
                            sb.AppendFormat("{0} {1}", point.X, point.Y);
                        }
                    }
                    obj = sb.ToString();
                }
                catch (Exception ex)
                {
                    LogHelper.WriteErrorSync(ex);
                }
            }
            return obj;
        }
        #endregion
    }

    [Serializable]
    public class Cell
    {
        public static readonly IXmlRuntimeFormatter Formatter = new XmlFormatter();
        public static readonly IXmlStringSerializer<Cell> XmlSerializer = new XmlUnicodeStringSerializer<Cell>();

        private int _value;
        private int _x;
        private int _y;
        private CellReferenceCollection _references = new CellReferenceCollection();

        public CellReferenceCollection Collection
        {
            get
            {
                return _references;
            }
            set
            {
                _references = value;
            }
        }
        public int X { get { return _x; } set { _x = value; } }
        public int Y { get { return _y; } set { _y = value; } }
        public int Value { get { return _value; } set { _value = value; } }

        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "[{0};{1}] {2} ({3})", _x, _y, _value, ReferencesToString());
        }

        private string ReferencesToString()
        {
            var sb = new StringBuilder();
            var references = new List<int>(_references);
            references.Sort();
            foreach (int value in references)
            {
                sb.Append(value);
            }
            return sb.ToString();
        }

        #region Nested type: XmlFormatter
        private class XmlFormatter : XmlTypeSerializer<Cell>
        {
            public XmlFormatter() : base(true) { }
            public override void AddOverrides(IXmlTypeFormatter formatter)
            {
                AddType(formatter, "cell");
                AddAttribute(formatter, "X", "x");
                AddAttribute(formatter, "Y", "y");
                AddAttribute(formatter, "Value", "value");
                AddElement(formatter, "Collection", "references");
            }
        }
        #endregion
    }

    [Serializable]
    public class CellReferenceCollection : List<int>, IXmlSerializable
    {
        #region IXmlSerializable Members
        public XmlSchema GetSchema() { return null; }
        public void ReadXml(XmlReader reader)
        {
            try
            {
                if (reader.IsStartElement("references"))
                {
                    if (reader.MoveToAttribute("available"))
                    {
                        if (reader.ReadAttributeValue())
                        {
                            foreach (char ch in reader.Value)
                            {
                                int value = ch - '0';
                                if (value >= 1 && value <= 9)
                                {
                                    if (!Contains(value))
                                    {
                                        Add(value);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrorSync(ex);
            }
        }
        public void WriteXml(XmlWriter writer)
        {
            try
            {
                var sb = new StringBuilder();
                foreach (int value in this)
                {
                    sb.Append((char)('0' + value));
                }
                if (sb.Length > 0) writer.WriteAttributeString("available", sb.ToString());
            }
            catch (Exception ex)
            {
                LogHelper.WriteErrorSync(ex);
            }
        }
        #endregion
    }
}
